{*******************************************************************************
*  Copyright (c) 2021 Jesse Jin Authors. All rights reserved.                  *
*                                                                              *
*  Use of this source code is governed by a MIT-style                          *
*  license that can be found in the LICENSE file.                              *
*                                                                              *
*  版权由作者 Jesse Jin 所有。                                                 *
*  此源码的使用受 MIT 开源协议约束，详见 LICENSE 文件。                        *
*******************************************************************************}

unit DM_Main;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, uConfig, ExtCtrls, Menus, SQLite3Conn, SQLDB, Forms;

type
  TTask = record
    ID: integer;
    TaskName: string;
    Remark: string;
    PreNum: integer;
    UsedNum: integer;
    InvalidNum: integer;
    State: integer;
  end;

  { TdmMain }

  TdmMain = class(TDataModule)
    conDB: TSQLite3Connection;
    miExit: TMenuItem;
    pmMain: TPopupMenu;
    qryPub: TSQLQuery;
    SQLTran: TSQLTransaction;
    tiMain: TTrayIcon;
    procedure DataModuleCreate(Sender: TObject);
    procedure DataModuleDestroy(Sender: TObject);
    procedure miExitClick(Sender: TObject);
    procedure tiMainClick(Sender: TObject);
  private
    FConfig: TConfig;
    //连接数据库
    procedure ConnectDB;
    //断开数据库
    procedure DisconnectDB;
  public
    //查询数据
    procedure QueryData(AQuery: TSQLQuery; ASQL: string);
    //显示当天任务
    procedure ShowTodayTask(AQuery: TSQLQuery);
    //显示任务明细
    procedure ShowTaskDetail(AQuery: TSQLQuery; ATaskID, AKind: integer);
    //显示理由
    procedure ShowReason(AQuery: TSQLQuery; AKind: integer);
    //新建任务
    function NewTask(ATask: TTask): integer;
    //修改任务
    procedure ModfyTask(ATask: TTask);
    //作废任务
    procedure DelTask(ATaskID: integer);
    //开始番茄钟
    function StartTomato(ATaskID: integer): integer;
    //中止番茄钟
    procedure StopTomato(ATomatoID: integer; ARemark: string = '');
    //工作时间结束
    procedure EndWork(ATomatoID: integer);
    //休息时间结束
    procedure EndSleep(ATomatoID: integer);
    //新增理由
    procedure NewReason(AReason: string);
    //删除理由
    procedure DelReason(AID: integer);
    //配置
    property Config: TConfig read FConfig;
  end;

var
  dmMain: TdmMain;

implementation

uses
  uLogger;

{$R *.lfm}

{ TdmMain }

procedure TdmMain.DataModuleCreate(Sender: TObject);
begin
  tiMain.Icon := Application.Icon;
  tiMain.Hint := Application.Title;
  FConfig := TConfig.Create;
  FConfig.Load;
  ConnectDB;
end;

procedure TdmMain.DataModuleDestroy(Sender: TObject);
begin
  DisconnectDB;
  FConfig.Save;
  FConfig.Free;
end;

procedure TdmMain.miExitClick(Sender: TObject);
begin
  Application.Terminate;
end;

procedure TdmMain.tiMainClick(Sender: TObject);
begin
  Application.MainForm.Visible := not Application.MainForm.Visible;
end;

procedure TdmMain.ConnectDB;
begin
  conDB.DatabaseName := Config.Path + 'Tomato.db';
  if conDB.Connected then
    try
      conDB.Open;
    except
      on E: Exception do
        TLogger.AddErrorLog(Format('数据库连接出错：%s', [E.Message]));
    end;
end;

procedure TdmMain.DisconnectDB;
begin
  if conDB.Connected then
    conDB.Close();
end;

procedure TdmMain.QueryData(AQuery: TSQLQuery; ASQL: string);
begin
  AQuery.DisableControls;
  try
    try
      AQuery.Close;
      AQuery.SQL.Text := ASQL;
      AQuery.Open;
    except
      on E: Exception do
        TLogger.AddErrorLog(Format('【%s】查询出错：%s', [ASQL, E.Message]));
    end;
  finally
    AQuery.EnableControls;
  end;
end;

procedure TdmMain.ShowTodayTask(AQuery: TSQLQuery);
const
  fmtSQL =
    'SELECT * FROM "Task" WHERE (CreateTime BETWEEN ''%s'' AND ''%s'') OR (State IN(0,1));';
var
  sqlstr: string;
begin
  sqlstr := Format(fmtSQL, [FormatDateTime('YYYY-MM-DD 00:00:00', Now()),
    FormatDateTime('YYYY-MM-DD 23:59:59', Now())]);
  QueryData(AQuery, sqlstr);
end;

procedure TdmMain.ShowTaskDetail(AQuery: TSQLQuery; ATaskID, AKind: integer);
const
  fmtSQL0 =
    'SELECT * FROM "Tomato" WHERE TaskID=%d ORDER BY ID;';
  fmtSQL1 =
    'SELECT * FROM "Tomato" WHERE (TaskID=%d) AND (State<>3) ORDER BY ID;';
  fmtSQL2 =
    'SELECT * FROM "Tomato" WHERE (TaskID=%d) AND (State=3) ORDER BY ID;';
var
  sqlstr: string;
begin
  case AKind of
    1: sqlstr := fmtSQL1;
    2: sqlstr := fmtSQL2;
    else
      sqlstr := fmtSQL0;
  end;
  sqlstr := Format(sqlstr, [ATaskID]);
  QueryData(AQuery, sqlstr);
end;

procedure TdmMain.ShowReason(AQuery: TSQLQuery; AKind: integer);
const
  fmtReasonSQL = 'SELECT * FROM "Misc" WHERE Kind=%d;';
var
  sqlstr: string;
begin
  sqlstr := Format(fmtReasonSQL, [AKind]);
  QueryData(AQuery, sqlstr);
end;

function TdmMain.NewTask(ATask: TTask): integer;
const
  fmtTaskSQL =
    'INSERT INTO Task(TaskName,Remark,PreNum,CreateTime) VALUES(''%s'',''%s'',%d,DATETIME(''now'',''localtime''));';
  fmtTaskIdSQL = 'SELECT LAST_INSERT_ROWID() RID;';
var
  sqlstr: string;
begin
  qryPub.Close;
  try
    sqlstr := Format(fmtTaskSQL, [ATask.TaskName, ATask.Remark, ATask.PreNum]);
    qryPub.SQL.Text := sqlstr;
    qryPub.ExecSQL;
    qryPub.SQL.Text := fmtTaskIdSQL;
    qryPub.Open;
    Result := qryPub.FieldByName('RID').AsInteger;
  except
    on E: Exception do
      TLogger.AddErrorLog(Format('新建任务出错：%s', [E.Message]));
  end;
end;

procedure TdmMain.ModfyTask(ATask: TTask);
const
  fmtTaskSQL =
    'UPDATE Task SET Remark=''%1:s'',PreNum=%2:d,State=%3:d WHERE ID=%0:d;';
var
  sqlstr: string;
begin
  qryPub.Close;
  try
    sqlstr := Format(fmtTaskSQL, [ATask.ID, ATask.Remark, ATask.PreNum, ATask.State]);
    qryPub.SQL.Text := sqlstr;
    qryPub.ExecSQL;
  except
    on E: Exception do
      TLogger.AddErrorLog(Format('更新任务中作废番茄钟数出错：%s',
        [E.Message]));
  end;
end;

procedure TdmMain.DelTask(ATaskID: integer);
const
  fmtTaskSQL = 'UPDATE Task SET State=3 WHERE ID=%d;';
var
  sqlstr: string;
begin
  qryPub.Close;
  try
    sqlstr := Format(fmtTaskSQL, [ATaskID]);
    qryPub.SQL.Text := sqlstr;
    qryPub.ExecSQL;
  except
    on E: Exception do
      TLogger.AddErrorLog(Format('作废任务出错：%s', [E.Message]));
  end;
end;

function TdmMain.StartTomato(ATaskID: integer): integer;
const
  fmtTomatoSQL =
    'INSERT INTO Tomato(TaskID,BeginTime,State) VALUES(%d,DATETIME(''now'',''localtime''),0);';
  fmtTomatoIdSQL = 'SELECT LAST_INSERT_ROWID() RID;';
  fmtTaskSQL = 'UPDATE Task SET State=1 WHERE ID=%d;';
var
  sqlstr: string;
begin
  qryPub.Close;
  //开始番茄钟
  try
    sqlstr := Format(fmtTomatoSQL, [ATaskID]);
    qryPub.SQL.Text := sqlstr;
    qryPub.ExecSQL;
    qryPub.SQL.Text := fmtTomatoIdSQL;
    qryPub.Open;
    Result := qryPub.FieldByName('RID').AsInteger;
  except
    on E: Exception do
      TLogger.AddErrorLog(Format('开始番茄钟出错：%s', [E.Message]));
  end;
  //更新任务状态
  sqlstr := Format(fmtTaskSQL, [ATaskID]);
  qryPub.SQL.Text := sqlstr;
  try
    qryPub.ExecSQL;
  except
    on E: Exception do
      TLogger.AddErrorLog(Format('更新任务状态出错：%s', [E.Message]));
  end;
end;

procedure TdmMain.StopTomato(ATomatoID: integer; ARemark: string);
const
  fmtTomatoSQL1 =
    'UPDATE Tomato SET EndTime=DATETIME(''now'',''localtime''),Remark=''%1:s'',State=3 WHERE ID=%0:d;';
  fmtTomatoSQL2 =
    'UPDATE Tomato SET WorkLen=ROUND((JULIANDAY(EndTime)-JULIANDAY(BeginTime))*24*60) WHERE ID=%0:d;';
  fmtTaskSQL =
    'UPDATE Task SET InvalidNum=InvalidNum+1 WHERE ID IN(SELECT TaskID FROM Tomato WHERE ID=%d);';
var
  sqlstr: string;
begin
  qryPub.Close;
  //中止番茄钟
  try
    sqlstr := Format(fmtTomatoSQL1, [ATomatoID, ARemark]);
    qryPub.SQL.Text := sqlstr;
    qryPub.ExecSQL;
    sqlstr := Format(fmtTomatoSQL2, [ATomatoID]);
    qryPub.SQL.Text := sqlstr;
    qryPub.ExecSQL;
  except
    on E: Exception do
      TLogger.AddErrorLog(Format('中止番茄钟出错：%s', [E.Message]));
  end;
  //更新任务中作废番茄钟数
  try
    sqlstr := Format(fmtTaskSQL, [ATomatoID]);
    qryPub.SQL.Text := sqlstr;
    qryPub.ExecSQL;
  except
    on E: Exception do
      TLogger.AddErrorLog(Format('更新任务中作废番茄钟数出错：%s',
        [E.Message]));
  end;
end;

procedure TdmMain.EndWork(ATomatoID: integer);
const
  fmtTomatoSQL1 =
    'UPDATE Tomato SET EndTime=DATETIME(''now'',''localtime''),State=1 WHERE ID=%0:d;';
  fmtTomatoSQL2 =
    'UPDATE Tomato SET WorkLen=ROUND((JULIANDAY(EndTime)-JULIANDAY(BeginTime))*24*60) WHERE ID=%0:d;';
  fmtTaskSQL =
    'UPDATE Task SET UsedNum=UsedNum+1 WHERE ID IN(SELECT TaskID FROM Tomato WHERE ID=%d);';
var
  sqlstr: string;
begin
  qryPub.Close;
  //记录番茄钟工作完成
  try
    sqlstr := Format(fmtTomatoSQL1, [ATomatoID]);
    qryPub.SQL.Text := sqlstr;
    qryPub.ExecSQL;
    sqlstr := Format(fmtTomatoSQL2, [ATomatoID]);
    qryPub.SQL.Text := sqlstr;
    qryPub.ExecSQL;
  except
    on E: Exception do
      TLogger.AddErrorLog(Format('记录番茄钟工作完成出错：%s',
        [E.Message]));
  end;
  //更新任务中使用的番茄钟数
  try
    sqlstr := Format(fmtTaskSQL, [ATomatoID]);
    qryPub.SQL.Text := sqlstr;
    qryPub.ExecSQL;
  except
    on E: Exception do
      TLogger.AddErrorLog(Format('更新任务中使用的番茄钟数出错：%s',
        [E.Message]));
  end;
end;

procedure TdmMain.EndSleep(ATomatoID: integer);
const
  fmtTomatoSQL =
    'UPDATE Tomato SET SleepLen=ROUND((JULIANDAY(DATETIME(''now'',''localtime''))-JULIANDAY(EndTime))*24*60),State=2 WHERE ID=%0:d;'
  ;
var
  sqlstr: string;
begin
  qryPub.Close;
  try
    sqlstr := Format(fmtTomatoSQL, [ATomatoID]);
    qryPub.SQL.Text := sqlstr;
    qryPub.ExecSQL;
  except
    on E: Exception do
      TLogger.AddErrorLog(Format('记录番茄钟休息完成出错：%s', [E.Message]));
  end;
end;

procedure TdmMain.NewReason(AReason: string);
const
  fmtReasonSQL = 'INSERT INTO Misc(Content) VALUES(''%s'');';
var
  sqlstr: string;
begin
  qryPub.Close;
  try
    sqlstr := Format(fmtReasonSQL, [AReason]);
    qryPub.SQL.Text := sqlstr;
    qryPub.ExecSQL;
  except
    on E: Exception do
      TLogger.AddErrorLog(Format('新增理由出错：%s', [E.Message]));
  end;
end;

procedure TdmMain.DelReason(AID: integer);
const
  fmtReasonSQL = 'DELETE FROM Misc WHERE ID=%d;';
var
  sqlstr: string;
begin
  qryPub.Close;
  try
    sqlstr := Format(fmtReasonSQL, [AID]);
    qryPub.SQL.Text := sqlstr;
    qryPub.ExecSQL;
  except
    on E: Exception do
      TLogger.AddErrorLog(Format('删除理由出错：%s', [E.Message]));
  end;
end;

end.
